﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Net;
using ERF;
using FirstPerson.Objects;
using FirstPerson.States;

namespace FirstPerson
{
    public class NetworkPacket : IComparable
    {
        private NetworkHandler.packetType type;
        private object value;
        public NetworkPacket(NetworkHandler.packetType type, object value)
        {
            this.type = type;
            this.value = value;
        }
        public NetworkHandler.packetType Type
        {
            get { return this.type; }
        }
        public object Value
        {
            get { return this.value; }
        }

        #region IComparable Members

        public int CompareTo(object obj)
        {
            NetworkPacket other = obj as NetworkPacket;
            if (other != null)
                return this.type.CompareTo(other.type);
            throw new ArgumentException("Object is not a NetworkPacket");
        }

        #endregion
    }
    /// <summary>
    /// Class for handling all the network in the game. sends and recives all data, also contains functions for
    /// setting up a new network by inheriting it from the ERF.NetworkHandler.
    /// </summary>
    public class NetworkHandler : ERF.NetworkHandler
    {
        public enum packetType
        {
            Position = 1,
            Rotation = 2,
            Velocity = 4,
            BeginFire = 8,
            EndFire = 16,
            Weapon = 32,
            Pickup = 64,
            Killed = 128,
            Hit = 256,
            GameData = 512
        };
        protected List<NetworkPacket> sendBuffer, sendReliableBuffer;
        public NetworkHandler(ERFGame game)
            : base(game)
        {
            this.HookNetwork();
            this.sendBuffer = new List<NetworkPacket>();
            this.sendReliableBuffer = new List<NetworkPacket>();
        }
        /// <summary>
        /// Binds the events to the functions
        /// </summary>
        private void HookNetwork()
        {
            base.GameHosted += new EventHandler(network_GameHosted);
            base.GameJoined += new EventHandler(network_GameJoined);
            base.GamerJoined += new EventHandler<GamerJoinedEventArgs>(network_GamerJoined); ;
            base.GamerLeft += new EventHandler<GamerLeftEventArgs>(network_GamerLeft);
            base.GameStarted += new EventHandler<GameStartedEventArgs>(NetworkHandler_GameStarted);
            base.GameEnded += new EventHandler<GameEndedEventArgs>(NetworkHandler_GameEnded); 
            //base.HostChanged += new EventHandler<HostChangedEventArgs>(network_HostChanged);
        }

        void NetworkHandler_GameEnded(object sender, GameEndedEventArgs e)
        {
            GameConsole.GetInstance.AddMessage("Game over");
        }

        void NetworkHandler_GameStarted(object sender, GameStartedEventArgs e)
        {
            GameConsole.GetInstance.AddMessage("Game started");
        }
        /// <summary>
        /// Event that is called when a game is hosted. Prints the status on screen
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void network_GameHosted(object sender, EventArgs e)
        {
            GameConsole.GetInstance.AddMessage("Hosted a networkgame!");
        }
        /// <summary>
        /// Event that is called when the player joins a networkgame. And prints it on screen
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void network_GameJoined(object sender, EventArgs e)
        {
            GameConsole.GetInstance.AddMessage("Joined a networkgame!");
        }
        /// <summary>
        /// Event that is called when a player joins your networkgame. prints it on screen
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void network_GamerJoined(object sender, GamerJoinedEventArgs e)
        {
            if (!e.Gamer.IsLocal)
                GameConsole.GetInstance.AddMessage(e.Gamer.Gamertag + " joined the game!");
        }

        /// <summary>
        /// event that is called when a gamer has left and prints it on the screen
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void network_GamerLeft(object sender, GamerLeftEventArgs e)
        {
            if (!e.Gamer.IsLocal)
                GameConsole.GetInstance.AddMessage(e.Gamer.Gamertag + " left the game!");
        }

        public bool IsHost
        {
            get { return this.networkSession.IsHost; }
        }

        public void Send(packetType type, object value)
        {
            this.sendBuffer.Add(new NetworkPacket(type, value));
        }
        public void SendReliable(packetType type, object value)
        {
            this.sendReliableBuffer.Add(new NetworkPacket(type, value));
        }

        /// <summary>
        /// This function recives all multiplayerdata from other players. As long as there
        /// is data available it will keep reciving and allocating the recived message data to the senders
        /// gamingobject that is recived from the gamer.tag.
        /// </summary>
        /// <param name="gamer"></param>
        protected override void RecieveData(LocalNetworkGamer gamer)
        {
            while (gamer.IsDataAvailable)
            {
                NetworkGamer sender;
                gamer.ReceiveData(this.packetReader, out sender);
                if (!sender.IsLocal)
                {
                    NetworkPlayer player = (NetworkPlayer)sender.Tag;
                    packetType type = (packetType)this.packetReader.ReadUInt16();
                    if ((type & packetType.Position) > 0)
                        player.Position = this.packetReader.ReadVector3();
                    if ((type & packetType.Rotation) > 0)
                    {
                        Vector2 v = this.packetReader.ReadVector2();
                        player.Rotation = new Vector3(player.Rotation.X, v.X, player.Rotation.Z);
                        player.CurrentWeapon.Rotation = new Vector3(v.Y, player.CurrentWeapon.Rotation.Y, player.CurrentWeapon.Rotation.Z);
                    }
                    if ((type & packetType.Velocity) > 0)
                        player.PhysicsBody.velocity = this.packetReader.ReadVector3();

                    if ((type & packetType.BeginFire) > 0)
                    {
                        this.packetReader.ReadByte();
                        player.CurrentWeapon.BeginShooting();
                    }
                    if ((type & packetType.EndFire) > 0)
                    {
                        this.packetReader.ReadByte();
                        player.CurrentWeapon.EndShooting();
                    }
                    if ((type & packetType.Killed) > 0)
                    {
                        byte target = this.packetReader.ReadByte();
                        (this.networkSession.FindGamerById(target).Tag as Player).Killed(sender.Tag as Player);
                    }
                    if ((type & packetType.Hit) > 0)
                    {
                        byte dmg = this.packetReader.ReadByte();
                        byte id = this.packetReader.ReadByte();
                        try
                        {
                            (this.networkSession.FindGamerById(id).Tag as Player).TakeDamage(dmg, player);
                        }
                        catch (Exception e)
                        {
                            string message = e.ToString();
                        }
                    }
                    if ((type & packetType.GameData) > 0)
                    {
                        Gametypes.GameData data = new FirstPerson.Gametypes.GameData();
                        data.randomSeed = this.packetReader.ReadInt32();
                        data.startTime = this.packetReader.ReadDouble();
                        data.killLimit = this.packetReader.ReadInt16();
                        data.timeLimit = this.packetReader.ReadInt16();
                        data.mapName = this.packetReader.ReadString();
                        data.gameTypeNumber = (Gametypes.GameTypeNumber)this.packetReader.ReadByte();

                        JoinState.GetInstance(((ERFGame)this.Game).StateMachine).JoinGame(data);
                    }
                }
            }
        }

        public void SendGameData(LocalNetworkGamer local, NetworkGamer gamer, Gametypes.GameData data)
        {
            this.packetWriter.Write((ushort)packetType.GameData);
            this.packetWriter.Write(data.randomSeed);
            this.packetWriter.Write(data.startTime);
            this.packetWriter.Write(data.killLimit);
            this.packetWriter.Write(data.timeLimit);
            this.packetWriter.Write(data.mapName);
            this.packetWriter.Write((byte)data.gameTypeNumber);
            local.SendData(this.packetWriter, SendDataOptions.ReliableInOrder, gamer);
        }

        /// <summary>
        /// Sends all the data from the local player.
        /// It sends position every 7th time and the force every other time make the physics engine
        /// work more. The player data is recived from the gamer.Tag object
        /// </summary>
        /// <param name="gamer"></param>
        protected override void SendData(LocalNetworkGamer gamer)
        {
            Player player = (Player)gamer.Tag;
            this.sendBuffer.Add(new NetworkPacket(packetType.Position, player.Position));
            this.sendBuffer.Add(new NetworkPacket(packetType.Rotation, new Vector2(player.Rotation.Y, player.CurrentWeapon.Rotation.X)));

            this.SendDataBuffer(gamer, this.sendBuffer, SendDataOptions.InOrder);
            this.sendBuffer.Clear();

            this.SendDataBuffer(gamer, this.sendReliableBuffer, SendDataOptions.ReliableInOrder);
            this.sendReliableBuffer.Clear();
        }

        protected void SendDataBuffer(LocalNetworkGamer gamer, List<NetworkPacket> buffer, SendDataOptions options)
        {
            if (buffer.Count > 0)
            {
                packetType type = 0;
                buffer.Sort();
                foreach (NetworkPacket packet in buffer)
                    type |= packet.Type;
                packetWriter.Write((ushort)type);
                foreach (NetworkPacket packet in buffer)
                    switch (packet.Type)
                    {
                        case packetType.Position: this.packetWriter.Write((Vector3)packet.Value); break;
                        case packetType.Rotation: this.packetWriter.Write((Vector2)packet.Value); break;
                        case packetType.Velocity: this.packetWriter.Write((Vector3)packet.Value); break;
                        case packetType.BeginFire: this.packetWriter.Write((byte)packet.Value); break;
                        case packetType.EndFire: this.packetWriter.Write((byte)packet.Value); break;
                        case packetType.Killed: this.packetWriter.Write((byte)packet.Value); break;
                        case packetType.Hit: this.packetWriter.Write((short)packet.Value); break;
                        case packetType.GameData:
                            Gametypes.GameData data = (Gametypes.GameData)packet.Value;
                            this.packetWriter.Write(data.randomSeed);
                            this.packetWriter.Write(data.startTime);
                            this.packetWriter.Write(data.killLimit);
                            this.packetWriter.Write(data.timeLimit);
                            this.packetWriter.Write(data.mapName);
                            this.packetWriter.Write((byte)data.gameTypeNumber);
                            break;
                    }
                gamer.SendData(this.packetWriter, options);
            }
        }
        /// <summary>
        /// prints the current traficdata on the screen in byte/sec for both send and recive
        /// </summary>
        public void TestSendData()
        {
            try
            {
                GameConsole.GetInstance.AddMessage("Send: " + this.networkSession.BytesPerSecondSent + "  Recive: " + this.networkSession.BytesPerSecondReceived);
            }
            catch (Exception) { }
        }
    }
}
